package controller;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JTextArea;
import model.Arquivo;
import model.MaquinaTuring;
import model.Transicao;

/**
 * @author lucasamaral
 * @author fernando
 */
public class MaquinaTuringController {

  private MaquinaTuring maquinaTuring = new MaquinaTuring();
  private Collection<Transicao> transições = new ArrayList<Transicao>();
  private Arquivo arquivo = new Arquivo();
  private Integer posiçãoCabeçaLeitura = 0;
  private String estadoAtual = "q1";
  private String simboloAtual;
  private String situaçãoMaquina;
  
  /**
   * Método get da instância da classe MaquinaTuring
   * @return Retorna um objeto do tipo MaquinaTuring
   */
  public MaquinaTuring getMaquinaTuring() {
    return maquinaTuring;
  }

  /**
   * Método set da instância da classe MaquinaTuring
   * @param maquinaTuring Recebe um objeto do tipo maquinaTuring
   */
  public void setMaquinaTuring(MaquinaTuring maquinaTuring) {
    this.maquinaTuring = maquinaTuring;
  }

  /**
   * Método get da Collection do tipo Transições
   * @return Retorna um objeto do tipo Transições
   */
  public Collection<Transicao> getTransições() {
    return transições;
  }

  /**
   * Método set da Collection do tipo Transições
   * @param transicões Recebe um objeto do tipo Transições
   */
  public void setTransições(Collection<Transicao> transicões) {
    this.transições = transicões;
  }

  /**
   * Método get da Collection do tipo PosiçãoCabeçaLeitura
   * @return Retorna um objeto do tipo PosiçãoCabeçaLeitura
   */
  public Integer getPosiçãoCabeçaLeitura() {
    return posiçãoCabeçaLeitura;
  }

  /**
   * Método set da Collection do tipo PosiçãoCabeçaLeitura
   * @param posiçãoCabeçaLeitura Recebe um objeto do tipo PosiçãoCabeçaLeitura
   */
  public void setPosiçãoCabeçaLeitura(Integer posiçãoCabeçaLeitura) {
    this.posiçãoCabeçaLeitura = posiçãoCabeçaLeitura;
  }

  /**
   * Método get da Collection do tipo EstadoAtual
   * @return Retorna um objeto do tipo EstadoAtual
   */
  public String getEstadoAtual() {
    return estadoAtual;
  }

  /**
   * Método set da Collection do tipo EstadoAtual
   * @param estadoAtual Recebe um objeto do tipo EstadoAtual
   */
  public void setEstadoAtual(String estadoAtual) {
    this.estadoAtual = estadoAtual;
  }

  /**
   * Método get da Collection do tipo SimboloAtual
   * @return Retorna um objeto do tipo SimboloAtual
   */
  public String getSimboloAtual() {
    return simboloAtual;
  }

  /**
   * Método set da Collection do tipo SimboloAtual
   * @param simboloAtual Recebe um objeto do tipo SimboloAtual
   */
  public void setSimboloAtual(String simboloAtual) {
    this.simboloAtual = simboloAtual;
  }

  /**
   * Método get da Collection do tipo SituaçãoMaquina
   * @return Retorna um objeto do tipo SituaçãoMaquina
   */
  public String getSituaçãoMaquina() {
    return situaçãoMaquina;
  }

  /**
   * Método set da Collection do tipo SituaçãoMaquina
   * @param situaçãoMaquina Recebe um objeto do tipo SituaçãoMaquina
   */
  public void setSituaçãoMaquina(String situaçãoMaquina) {
    this.situaçãoMaquina = situaçãoMaquina;
  }

  /**
   * Método get da Collection do tipo Arquivo
   * @return Retorna um objeto do tipo Arquivo
   */
  public Arquivo getArquivo() {
    return arquivo;
  }

  /**
   * Método set da Collection do tipo Arquivo
   * @param arquivo Recebe um objeto do tipo Arquivo
   */
  public void setArquivo(Arquivo arquivo) {
    this.arquivo = arquivo;
  }

  /**
   * Método que executa a MT e a controla.
   * @param jTextArea Recebe um objeto do tipo JTextArea
   */
  public void testaMaquina(JTextArea jTextArea) {
    int numeroFita = 1;
    Map<Integer, String> resultados = new HashMap<Integer, String>();
    for (String[] fita : getMaquinaTuring().getCasosDeTeste()) {
      jTextArea.append("--------Fita " + numeroFita + "---------\n");
      if (fitaValida(fita)) {
        setEstadoAtual("q1");
        setPosiçãoCabeçaLeitura(0);
        setSituaçãoMaquina("Continua");
        setSimboloAtual(fita[posiçãoCabeçaLeitura]);
        while (situaçãoMaquina.equals("Continua")) {
          situaçãoMaquina = executaTransição(fita, jTextArea);
        }
        if (maquinaTuring.getTipoMaquina().equals(MaquinaTuring.TIPO_PROCESSADORA_FUNCAO)) {
          resultados.put(numeroFita, getExibeFita(fita));
          if (!estadoAtual.equals("qac")) {
            resultados.put(numeroFita, situaçãoMaquina);
          }
        } else if (maquinaTuring.getTipoMaquina().equals(MaquinaTuring.TIPO_RECONHECEDORA_LINGUAGEM)) {
          resultados.put(numeroFita, situaçãoMaquina);
        }
        jTextArea.append(getExibeFita(fita) + "\n");
      }
      numeroFita++;
    }
    getArquivo().gravaDados(resultados);
  }

  /**
   * Método que executa a transição da MT e a controla.
   * @param fita Recebe um Array do tipo String[]
   * @param jTextArea Recebe um objeto do tipo JTextArea
   */
  private String executaTransição(String[] fita, JTextArea jTextArea) {
    if (getMaquinaTuring().getAlfabetoFita().contains(getSimboloAtual())) {
      for (Transicao transicao : transições) {
        if (transicao.getEstadoAtual().equals(getEstadoAtual()) && transicao.getSimboloAtual().equals(getSimboloAtual())) {
          if (getMaquinaTuring().getAlfabetoFita().contains(transicao.getProximoSimbolo())) {
            exibeTransicao(fita, transicao, jTextArea);
            setEstadoAtual(transicao.getProximoEstado());
            fita[getPosiçãoCabeçaLeitura()] = transicao.getProximoSimbolo();
            if (getEstadoAtual().equals("qac")) {
              return "Aceita";
            }
            if (transicao.getMovimenta().equals(Transicao.MOVIMENTA_DIREITA)) {
              posiçãoCabeçaLeitura++;
            } else {
              posiçãoCabeçaLeitura--;
            }
            if (getPosiçãoCabeçaLeitura() >= 0 && getPosiçãoCabeçaLeitura() < fita.length) {
              setSimboloAtual(fita[getPosiçãoCabeçaLeitura()]);
              return "Continua";
            }
          }
        }
      }
    }
    return "Rejeita";
  }
  
  /**
   * Método que exibe os detalhes da transição corrente da MT.
   * @param fita Recebe um Array do tipo String[]
   * @param transicao Recebe um objeto do tipo Transicao
   * @param jTextArea Recebe um objeto do tipo JTextArea
   */
  private void exibeTransicao(String[] fita, Transicao transicao, JTextArea jTextArea) {
    jTextArea.append(getExibeFita(fita) + "\n");
    jTextArea.append("(" + getEstadoAtual() + "," + getSimboloAtual() + ")=(" + transicao.getProximoEstado() 
            + "," + transicao.getProximoSimbolo() + "," + transicao.getMovimenta() + ")\n");
  }
  
  /**
   * Método que exibe na interface a fita após a transição corrente.
   * @param fita Recebe um Array do tipo String[]
   */
  private String getExibeFita(String[] fita) {
    String fitaFinal = "";
    for (String simbolo : fita) {
      fitaFinal += simbolo + "|";
    }
    return fitaFinal + "B";
  }
  
  /**
   * Método que verifica se as posições da fita possuem símbolos válidos.
   * @param fita Recebe um Array do tipo String[]
   */
  private Boolean fitaValida(String[] fita) {
    for (String simbolo : fita) {
      if (!getMaquinaTuring().getAlfabetoEntrada().contains(simbolo) && simbolo != null) {
        return false;
      }
    }
    fita[fita.length - 1] = "B";
    return true;
  }
}
